//! HTTP protocol handler
//! 
//! This module implements HTTP/HTTPS protocol handling,
//! corresponding to HttpRunner's HTTP capabilities.

use super::{ProtocolHandler, ProtocolResult};
use crate::error::{Error, Result, NetworkError, ValidationError};
use crate::models::{
    step::{StepResult, StepContext, StepType},
    request::{HttpRequest, HttpResponse, HttpMethod},
    Value, ValueMap,
};
use async_trait::async_trait;
use reqwest::{Client, ClientBuilder};
use serde_json;
use std::collections::HashMap;
use std::time::{Duration, Instant};
use uuid::Uuid;

/// HTTP protocol handler
#[derive(Debug)]
pub struct HttpProtocolHandler {
    /// HTTP client
    client: Option<Client>,
    
    /// Handler configuration
    config: ValueMap,
}

impl HttpProtocolHandler {
    /// Create a new HTTP protocol handler
    pub fn new() -> Self {
        Self {
            client: None,
            config: HashMap::new(),
        }
    }
    
    /// Get or create HTTP client
    fn get_client(&self) -> Result<&Client> {
        self.client.as_ref().ok_or_else(|| {
            Error::Network(NetworkError::RequestFailed {
                url: "N/A".to_string(),
                status: 0,
            })
        })
    }
    
    /// Execute HTTP request
    async fn execute_request(&self, request: &HttpRequest) -> Result<HttpResponse> {
        let client = self.get_client()?;
        let start_time = Instant::now();

        // Build reqwest request
        let mut req_builder = match request.method {
            HttpMethod::GET => client.get(&request.url),
            HttpMethod::POST => client.post(&request.url),
            HttpMethod::PUT => client.put(&request.url),
            HttpMethod::DELETE => client.delete(&request.url),
            HttpMethod::HEAD => client.head(&request.url),
            HttpMethod::PATCH => client.patch(&request.url),
            HttpMethod::OPTIONS => client.request(reqwest::Method::OPTIONS, &request.url),
            HttpMethod::TRACE => client.request(reqwest::Method::TRACE, &request.url),
            HttpMethod::CONNECT => client.request(reqwest::Method::CONNECT, &request.url),
        };

        // Add headers
        for (key, value) in &request.headers {
            req_builder = req_builder.header(key, value);
        }

        // Add query parameters
        if !request.params.is_empty() {
            let query_params: Vec<(String, String)> = request.params
                .iter()
                .map(|(key, value)| {
                    let value_str = match value {
                        Value::String(s) => s.clone(),
                        Value::Number(n) => n.to_string(),
                        Value::Bool(b) => b.to_string(),
                        _ => serde_json::to_string(value).unwrap_or_default(),
                    };
                    (key.clone(), value_str)
                })
                .collect();
            req_builder = req_builder.query(&query_params);
        }

        // Add authentication
        if let Some(auth) = &request.auth {
            req_builder = self.apply_auth(req_builder, auth)?;
        }

        // Add body
        if let Some(json_body) = &request.json {
            req_builder = req_builder.json(json_body);
        } else if let Some(form_data) = &request.data {
            req_builder = req_builder.form(form_data);
        } else if let Some(raw_body) = &request.body {
            let body_str = match raw_body {
                Value::String(s) => s.clone(),
                _ => serde_json::to_string(raw_body)?,
            };
            req_builder = req_builder.body(body_str);
        }

        // Set timeout
        if let Some(timeout) = request.timeout {
            req_builder = req_builder.timeout(Duration::from_secs_f64(timeout));
        }

        // Execute request
        let response = req_builder.send().await
            .map_err(|_e| Error::Network(NetworkError::RequestFailed {
                url: request.url.clone(),
                status: 0,
            }))?;

        let elapsed = start_time.elapsed();
        let status_code = response.status().as_u16();
        let headers = response.headers().iter()
            .map(|(k, v)| (k.to_string(), v.to_str().unwrap_or("").to_string()))
            .collect();

        let response_url = response.url().to_string();
        let content_length = response.content_length();
        let content_type = response.headers()
            .get("content-type")
            .and_then(|v| v.to_str().ok())
            .map(|s| s.to_string());

        let text = response.text().await
            .map_err(|_e| Error::Network(NetworkError::RequestFailed {
                url: request.url.clone(),
                status: status_code,
            }))?;

        // Try to parse as JSON
        let json_value = if content_type.as_ref().map_or(false, |ct| ct.contains("application/json")) {
            serde_json::from_str(&text).ok()
        } else {
            None
        };

        Ok(HttpResponse {
            status_code,
            reason: crate::models::request::status_reason(status_code),
            headers,
            cookies: HashMap::new(), // TODO: Extract cookies
            text,
            content: Vec::new(), // TODO: Store raw content
            json: json_value,
            encoding: "utf-8".to_string(),
            url: response_url,
            elapsed,
            content_length,
            content_type,
            version: crate::models::request::HttpVersion::Http11, // TODO: Detect version
            ok: (200..300).contains(&status_code),
            history: Vec::new(), // TODO: Handle redirects
        })
    }

    /// Apply authentication to request
    fn apply_auth(&self, mut req_builder: reqwest::RequestBuilder, auth: &crate::models::request::AuthConfig) -> Result<reqwest::RequestBuilder> {
        match &auth.auth_type {
            crate::models::request::AuthType::Basic => {
                if let (Some(username), Some(password)) = (&auth.username, &auth.password) {
                    req_builder = req_builder.basic_auth(username, Some(password));
                }
            },
            crate::models::request::AuthType::Bearer => {
                if let Some(token) = &auth.token {
                    req_builder = req_builder.bearer_auth(token);
                }
            },
            crate::models::request::AuthType::ApiKey => {
                if let Some(api_key) = &auth.api_key {
                    req_builder = req_builder.header("X-API-Key", api_key);
                }
            },
            _ => {
                // TODO: Implement OAuth1, OAuth2, Custom auth
            }
        }
        Ok(req_builder)
    }

    /// Execute HTTP request with context
    async fn execute_request_with_context(&self, params: ValueMap, _context: &mut StepContext) -> Result<StepResult> {
        let start_time = Instant::now();
        let step_id = Uuid::new_v4();

        // Parse HTTP request from parameters
        let http_request: HttpRequest = serde_json::from_value(serde_json::to_value(params)?)
            .map_err(|e| Error::Network(NetworkError::InvalidUrl {
                url: format!("Invalid request format: {}", e),
            }))?;

        // Execute the HTTP request
        let response = self.execute_request(&http_request).await?;

        // Create step result
        let elapsed = start_time.elapsed();
        let mut response_data = HashMap::new();

        // Add response data
        response_data.insert("status_code".to_string(), serde_json::Value::Number((response.status_code as u32).into()));
        response_data.insert("headers".to_string(), serde_json::to_value(&response.headers)?);
        response_data.insert("text".to_string(), serde_json::Value::String(response.text.clone()));
        response_data.insert("url".to_string(), serde_json::Value::String(response.url.clone()));
        response_data.insert("elapsed_ms".to_string(), serde_json::Value::Number((elapsed.as_millis() as u64).into()));

        if let Some(json) = &response.json {
            response_data.insert("json".to_string(), json.clone());
        }

        // HTTP statistics
        let mut http_stat = HashMap::new();
        http_stat.insert("status_code".to_string(), response.status_code as i64);
        http_stat.insert("response_time_ms".to_string(), elapsed.as_millis() as i64);
        http_stat.insert("content_length".to_string(), response.content_length.unwrap_or(0) as i64);

        Ok(StepResult {
            id: step_id,
            name: format!("{} {}", http_request.method, http_request.url),
            start_time: std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_millis() as i64,
            step_type: StepType::Request,
            success: response.ok,
            elapsed: elapsed.as_millis() as i64,
            http_stat: Some(http_stat),
            data: Some(serde_json::to_value(response_data)?),
            content_size: response.content_length.unwrap_or(0) as i64,
            export_vars: HashMap::new(),
            actions: Vec::new(),
            attachments: None,
            error: None,
            validation_results: Vec::new(),
        })
    }
}

#[async_trait]
impl ProtocolHandler for HttpProtocolHandler {
    fn protocol_name(&self) -> &str {
        "http"
    }

    fn supported_operations(&self) -> Vec<String> {
        vec![
            "request".to_string(),
            "get".to_string(),
            "post".to_string(),
            "put".to_string(),
            "delete".to_string(),
            "patch".to_string(),
            "head".to_string(),
            "options".to_string(),
        ]
    }

    async fn initialize(&mut self, config: ValueMap) -> Result<()> {
        self.config = config;

        // Create HTTP client with configuration
        let mut client_builder = ClientBuilder::new();

        // Configure timeout
        if let Some(timeout) = self.config.get("timeout").and_then(|v| v.as_f64()) {
            client_builder = client_builder.timeout(Duration::from_secs_f64(timeout));
        }

        // Configure SSL verification
        if let Some(verify_ssl) = self.config.get("verify_ssl").and_then(|v| v.as_bool()) {
            client_builder = client_builder.danger_accept_invalid_certs(!verify_ssl);
        }

        // Configure HTTP/2
        if let Some(http2) = self.config.get("http2").and_then(|v| v.as_bool()) {
            if http2 {
                client_builder = client_builder.http2_prior_knowledge();
            }
        }

        // Configure proxy
        if let Some(proxy_url) = self.config.get("proxy").and_then(|v| v.as_str()) {
            if let Ok(proxy) = reqwest::Proxy::all(proxy_url) {
                client_builder = client_builder.proxy(proxy);
            }
        }

        // Configure user agent
        if let Some(user_agent) = self.config.get("user_agent").and_then(|v| v.as_str()) {
            client_builder = client_builder.user_agent(user_agent);
        }

        // Build client
        self.client = Some(client_builder.build().map_err(|_e| {
            Error::Network(NetworkError::RequestFailed {
                url: "Failed to create HTTP client".to_string(),
                status: 0,
            })
        })?);

        Ok(())
    }

    async fn execute(&self, operation: &str, params: ValueMap, context: &mut StepContext) -> Result<StepResult> {
        match operation {
            "request" => self.execute_request_with_context(params, context).await,
            "get" => {
                let mut request_params = params;
                request_params.insert("method".to_string(), Value::String("GET".to_string()));
                self.execute_request_with_context(request_params, context).await
            },
            "post" => {
                let mut request_params = params;
                request_params.insert("method".to_string(), Value::String("POST".to_string()));
                self.execute_request_with_context(request_params, context).await
            },
            "put" => {
                let mut request_params = params;
                request_params.insert("method".to_string(), Value::String("PUT".to_string()));
                self.execute_request_with_context(request_params, context).await
            },
            "delete" => {
                let mut request_params = params;
                request_params.insert("method".to_string(), Value::String("DELETE".to_string()));
                self.execute_request_with_context(request_params, context).await
            },
            "patch" => {
                let mut request_params = params;
                request_params.insert("method".to_string(), Value::String("PATCH".to_string()));
                self.execute_request_with_context(request_params, context).await
            },
            "head" => {
                let mut request_params = params;
                request_params.insert("method".to_string(), Value::String("HEAD".to_string()));
                self.execute_request_with_context(request_params, context).await
            },
            "options" => {
                let mut request_params = params;
                request_params.insert("method".to_string(), Value::String("OPTIONS".to_string()));
                self.execute_request_with_context(request_params, context).await
            },
            _ => Err(Error::Network(NetworkError::InvalidUrl {
                url: format!("Unsupported operation: {}", operation),
            })),
        }
    }

    fn validate_params(&self, operation: &str, params: &ValueMap) -> Result<()> {
        // Check if URL is provided
        if !params.contains_key("url") {
            return Err(Error::Validation(ValidationError::FieldValidation {
                field: "url".to_string(),
                reason: format!("URL parameter is required for {} operation", operation)
            }));
        }

        // Validate URL format
        if let Some(url_value) = params.get("url") {
            if let Some(url_str) = url_value.as_str() {
                // Basic URL validation
                if !url_str.starts_with("http://") && !url_str.starts_with("https://") {
                    return Err(Error::Validation(ValidationError::SchemaValidation {
                        reason: "URL must start with http:// or https://".to_string()
                    }));
                }
            } else {
                return Err(Error::Validation(ValidationError::TypeValidation {
                    expected: "string".to_string(),
                    found: "other".to_string()
                }));
            }
        }

        Ok(())
    }
}
